package Core

import (
	"reflect"
	"fmt"
	"time"
	"net/http"
	"os"
	"log"
	"runtime/debug"
	"path"
)

const (
	CONFIG_FILE  = "config.json"
	RUN_MODE_DEV = "dev"
	RUN_MODE_PRO = "pro"
)

// struct tree if registered
var (
	RootDir string
	structTree map[string]reflect.Value
	funcTree map[string]reflect.Value
)

func init() {
	var e error
	RootDir, e = os.Getwd()
	if e != nil {
		panic(e)
	}
	structTree = make(map[string]reflect.Value)
	funcTree = make(map[string]reflect.Value)
	log.SetFlags(log.Ldate | log.Ltime)
}

// module interface
type HxModuleItf interface {
	Setup()
}

// Register module struct
func Register(s... HxModuleItf) {
	for _, v := range s {
		rv := reflect.ValueOf(v)
		keyString := fmt.Sprint(rv.Type())
		structTree[keyString] = rv
	}
}

// Register func
func SetFunc(name string, fn interface {}) {
	funcTree[name] = reflect.ValueOf(fn)
}

// Setup
func Setup(args...string) {
	defer panicExit()
	if len(structTree) < 1 {
		panic("no struct registered")
	}
	if len(args) > 0 {
		if args[0] == "" {
			args[0] = CONFIG_FILE
		}
		newConfig(args[0])
	}else {
		newConfig(CONFIG_FILE)
	}
	newRouter()
	newLogger()
	newDatabase(Config.String("database.driver"), Config.String("database.dsn"), Config.Int("database.max_connections"))
}

// http handler
type httpHandler struct {

}

// http handler func
func (this *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// serve static file
	staticFile := path.Join(RootDir, r.URL.Path)
	if f, e := os.Stat(staticFile); e == nil {
		if !f.IsDir() {
			http.ServeFile(w, r, staticFile)
			return
		}
	}
	// recover panic and do error handler
	defer func() {
		e := recover()
		if e != nil {
			log.Println(e)
			Logger.Err(fmt.Sprintln(e), string(debug.Stack()))
			Router.Err(w, r, fmt.Sprintln(e))
		}
	}()
	// do main handler
	res := Router.Do(w, r)
	if !res {
		Router.Err(w, r, "")
	}
}

// call module setup func
func callModuleSetup() {
	for _, v := range structTree {
		rm := v.MethodByName("Setup")
		rm.Call([]reflect.Value{})
		// log.Println("setup app module @ " + fmt.Sprint(v.Type()))
	}
	Router.Sync()
}

// Run
func Run() {
	defer panicExit()
	callModuleSetup()
	s := &http.Server{
		Addr:Config.StringOr("http.addr", "localhost:80"),
		Handler:&httpHandler{},
		ReadTimeout:    time.Duration(Config.IntOr("http.timeout", 30))*time.Second,
		WriteTimeout:   time.Duration(Config.IntOr("http.timeout", 30))*time.Second,
		MaxHeaderBytes: 1<<20,
	}
	log.Println("run start @ " + s.Addr)
	e := s.ListenAndServe()
	if e != nil {
		panic(e)
	}
}

// recover global panic and exit app
func panicExit() {
	e := recover()
	if e != nil {
		log.Println(e)
		Logger.Err(e, string(debug.Stack()))
		Logger.syncWrite()
		os.Exit(-1)
	}
}

